
#include <optional>
#include <memory>
#include <thread>
#include <chrono>
#include <windows.h>
#include <setupapi.h>
#include <devguid.h>
#include <regstr.h>
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string>
#include <dbt.h>
#include <locale>
#include <codecvt>
#include <time.h>

#pragma comment(lib, "Setupapi.lib")
#pragma comment(lib, "Setupapi.lib")

#include <flutter/plugin_registrar.h>
#include <flutter/basic_message_channel.h>
#include <flutter/standard_message_codec.h>
#include <flutter/ephemeral/flutter_messenger.h>
#include <flutter/ephemeral/flutter_windows.h>
#include <flutter/ephemeral/cpp_client_wrapper/include/flutter/flutter_engine.h>
#include "win32_window.h"

using namespace std;
static int watch_serial(FlutterDesktopMessengerRef messager);
static BOOL SelSerial(string devName);
static void notifySerialDev(FlutterDesktopMessengerRef messager);
static int read_serial(FlutterDesktopMessengerRef messager);

void log_bin(const void * data, int len) ;
void log_message(const char *format, ...);
std::string utf8_to_gbk(const char *src_str);

static FlutterDesktopMessengerRef g_messenger = nullptr;
static std::string g_cur_serial;
static HANDLE g_cur_dev = INVALID_HANDLE_VALUE;
static FlutterDesktopMessengerRef messengerLock() 
{
    auto messenger =  g_messenger;
    if (messenger == nullptr)   return nullptr;
    messenger = FlutterDesktopMessengerLock(messenger);
    if (FlutterDesktopMessengerIsAvailable(messenger))  return messenger;
    FlutterDesktopMessengerUnlock(messenger);
    return nullptr;
}
static void messengerUnlock(FlutterDesktopMessengerRef messenger) 
{
    FlutterDesktopMessengerUnlock(messenger);
}

static void receiveFlutterMsgOnSerialPlugin(
        FlutterDesktopMessengerRef /* messenger */,
        const FlutterDesktopMessage* /* message*/,
        void* /* user data */) ;
static int onSerialPlugWorkLoop(FlutterDesktopMessengerRef messager);
static void onSerialPlugWorkLoopBegin(void);
static void onSerialPlugWorkLoopEnd(void);
void serial_plugin_register(flutter::FlutterEngine* engine) 
{
    //std::cerr << "serial_plugin_register ---------" << std::endl;
    log_message("serial_plugin_register ---------\n");
    if (g_messenger != nullptr) {
        log_message("serial_plugin_register arleady\n");
        //std::cerr << "serial_plugin_register arleady" << std::endl;
        return ;
    }
    auto regRef = engine->GetRegistrarForPlugin("SerialPlugin");
    g_messenger = FlutterDesktopPluginRegistrarGetMessenger(regRef);
    FlutterDesktopMessengerSetCallback(g_messenger, "serial", receiveFlutterMsgOnSerialPlugin, nullptr);
    FlutterDesktopPluginRegistrarSetDestructionHandler(regRef, [](FlutterDesktopPluginRegistrarRef reg){
        g_messenger = nullptr;
    });
    auto t = std::thread([](){
        onSerialPlugWorkLoopBegin();
        while (true) {
            auto messenger =  messengerLock();
            if (messenger == nullptr)   break;
            auto sleepMillSec = onSerialPlugWorkLoop(messenger);
            messengerUnlock(messenger);
            std::this_thread::sleep_for(std::chrono::milliseconds(sleepMillSec));
        }
        onSerialPlugWorkLoopEnd();
    });
    t.detach();
    auto tt = std::thread([](){
        while (true) {
            auto messenger =  messengerLock();
            if (messenger == nullptr)   break;
            auto sleepMillSec = read_serial(messenger)<0?10:1;
            messengerUnlock(messenger);
            std::this_thread::sleep_for(std::chrono::milliseconds(sleepMillSec));
        }
    });
    tt.detach();
}

// ref flutter_messenger.h
static void receiveFlutterMsgOnSerialPlugin(
        FlutterDesktopMessengerRef messenger,
        const FlutterDesktopMessage* message,
        void* /* user data */) 
{
    // message->message, message->message_size;
    auto req = (const char *)message->message;
    //std::cerr << "app-cmd: " << req << std::endl;
    log_message("app-cmd: %s\n", req);
    if (SelSerial(req)) {
        extern HWND g_lastActiveHwnd;
        if (IsWindow(g_lastActiveHwnd)) SetWindowTextA(g_lastActiveHwnd, utf8_to_gbk(g_cur_serial.c_str()).c_str());
        notifySerialDev(messenger);
    }
    FlutterDesktopMessengerSendResponse(messenger, message->response_handle, (const uint8_t *)"", 0);
}

static void onSerialPlugWorkLoopBegin(void)
{
}

static void onSerialPlugWorkLoopEnd(void)
{
}

static int onSerialPlugWorkLoop(FlutterDesktopMessengerRef messager) 
{
    watch_serial(messager);

    return 10000;
}

struct SerialDeviceInfo {
    std::string name;
    int portNumber;
};

bool operator == (const SerialDeviceInfo &a, const SerialDeviceInfo & b) {
    return a.portNumber == b.portNumber && a.name == b.name;
}

static std::vector<SerialDeviceInfo> g_devices;

std::string gbk_to_utf8(const char *src_str)
{
	int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0);
	wchar_t* wstr = new wchar_t[len + 1];
	memset(wstr, 0, len + 1);
	MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len);
	len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
	char* str = new char[len + 1];
	memset(str, 0, len + 1);
	WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
	string strTemp = str;
	if (wstr) delete[] wstr;
	if (str) delete[] str;
	return strTemp;
}
std::string utf8_to_gbk(const char *src_str)
{
	int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
	wchar_t* wstr = new wchar_t[len + 1];
	memset(wstr, 0, len + 1);
	MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wstr, len);
	len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
	char* str = new char[len + 1];
	memset(str, 0, len + 1);
	WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
	string strTemp = str;
	if (wstr) delete[] wstr;
	if (str) delete[] str;
	return strTemp;
}

std::vector<SerialDeviceInfo> GetSerialDevices()
{
    std::vector<SerialDeviceInfo> devices;
    HDEVINFO hDevInfo;
    SP_DEVINFO_DATA deviceInfoData;
    DWORD i;

    hDevInfo = SetupDiGetClassDevs(NULL, 0, 0, DIGCF_PRESENT | DIGCF_ALLCLASSES);

    deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &deviceInfoData); i++)
    {
        DWORD dataType;
        char buffer[256] = {0};
        DWORD bufferSize = sizeof(buffer);
        if (SetupDiGetDeviceRegistryPropertyA(hDevInfo, &deviceInfoData, SPDRP_FRIENDLYNAME, &dataType,
                                             (PBYTE)buffer, bufferSize, &bufferSize)) {

            char * p = strstr(buffer, "COM");
            if (p == NULL)     continue;
            int portNum = atoi(p+3);
            if (portNum == 0)  continue;

            SerialDeviceInfo device;
            buffer[bufferSize] = 0;
            device.name = gbk_to_utf8(buffer);
            device.portNumber = portNum;
            devices.push_back(device);
        }
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
    return devices;
}

HANDLE OpenSerial(string portName)
{
    HANDLE hSerial = CreateFileA(portName.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hSerial == INVALID_HANDLE_VALUE)
    {
        return hSerial;
    }

    DCB dcbSerialParams;
    dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
    if (!GetCommState(hSerial, &dcbSerialParams))
    {
        CloseHandle(hSerial);
        return INVALID_HANDLE_VALUE;
    }
    dcbSerialParams.BaudRate = CBR_38400;
    dcbSerialParams.ByteSize = 8;
    dcbSerialParams.StopBits = ONESTOPBIT;
    dcbSerialParams.Parity = NOPARITY;
    if (!SetCommState(hSerial, &dcbSerialParams))
    {
        CloseHandle(hSerial);
        return INVALID_HANDLE_VALUE;
    }

    COMMTIMEOUTS timeouts;
    timeouts.ReadIntervalTimeout = 20;
    timeouts.ReadTotalTimeoutConstant = 100;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant = 100;
    timeouts.WriteTotalTimeoutMultiplier = 10;
    if (!SetCommTimeouts(hSerial, &timeouts))
    {
        CloseHandle(hSerial);
        return INVALID_HANDLE_VALUE;
    }

    return hSerial;
}

BOOL SelSerial(string devName)
{
    if (g_cur_serial == devName)
        return FALSE;
    CloseHandle(g_cur_dev);
    g_cur_dev = INVALID_HANDLE_VALUE;
    g_cur_serial = "";
    for (const auto &device : g_devices)
    {
        if (device.name == devName)
        {
            char name[30];
            sprintf_s(name, "\\\\.\\COM%d", device.portNumber);
            g_cur_dev = OpenSerial(name);
            if (g_cur_dev != INVALID_HANDLE_VALUE)
            {
                log_message("SelSerial: %s|%s\n", name, devName.c_str());
                g_cur_serial = devName;
                return TRUE;
            }
            return TRUE;
        }
    }
    log_message("SelSerial: not find %s\ncurrent dev:\n", devName.c_str());
    for (const auto &device : g_devices) {
        log_message("|%s|\n", device.name.c_str());
    }
    log_message("\n");
    return TRUE;
}

void onSerialDevChange(FlutterDesktopMessengerRef messager, std::vector<SerialDeviceInfo> && devs)
{
    if (devs == g_devices)  return ;
    g_devices = devs;
    notifySerialDev(messager);
}

void notifySerialDev(FlutterDesktopMessengerRef messager)
{
    std::string dlist = g_cur_serial;
    for (const auto &device : g_devices) {
        //std::cerr << device.name << "=|" << device.portNumber << std::endl;
        dlist += "\n";
        dlist += device.name;
    }
    log_message("notifySerialDev: %s\n", dlist.c_str());
    
    FlutterDesktopMessengerSend(messager, "serial", (const uint8_t *)dlist.c_str(), dlist.length());
}

static int watch_serial(FlutterDesktopMessengerRef messager)
{
    onSerialDevChange(messager, GetSerialDevices());
    return 0;
}

#pragma pack(1)
typedef struct lora_pack {
    uint8_t id[4];
    union {
        struct {
            uint16_t ack:1;
            uint16_t alrm:1;
            uint16_t ad:14;
            uint8_t e;
        } s;
        struct {
            uint8_t hi;
            uint8_t low;
            uint8_t e;
        } raw;
    };
} lora_pack_t;

#define SSTAG   0x3107
typedef struct {
    lora_pack_t rf;
    uint16_t    tag;
} utx_t;
#pragma pack()
void log_message(const char *format, ...);
void log_bin(const void * data, int len);

static int read_serial(FlutterDesktopMessengerRef messager)
{
    if (g_cur_dev == INVALID_HANDLE_VALUE)  return -1;
    union {
        uint8_t bytes[8];
        utx_t utx;
    } chBuf = {{0}};
    DWORD bytesRead = 0;
    BOOL nNumRead = ReadFile(g_cur_dev, &chBuf, sizeof(chBuf), &bytesRead, NULL);
    if (!nNumRead) {
        //std::cerr << "read_serial: " << "close = " << GetLastError() << std::endl;
        log_message("read_serial: close=%d\n", GetLastError());
        CloseHandle(g_cur_dev);
        g_cur_dev = OpenSerial(g_cur_serial);
        return -1;
    }
    if (bytesRead==0)                       return 0;
    //std::cerr << "read_serial: " << bytesRead << "=" << std::hex << (int)chBuf.bytes[0] << ":" << (int)chBuf.bytes[1] << ":" << (int)chBuf.bytes[2]  << ":" << (int)chBuf.bytes[3] << ":" << (int)chBuf.bytes[4]  << ":" << (int)chBuf.bytes[5] << ":" << (int)chBuf.bytes[6] << ":" << (int)chBuf.bytes[7] << std::endl;
    log_message("RX:%d=", bytesRead);
    log_bin(chBuf.bytes, bytesRead );
    if (bytesRead != sizeof(chBuf.utx))     return 0;
    uint8_t k = chBuf.utx.rf.raw.hi;
    chBuf.utx.rf.raw.hi = chBuf.utx.rf.raw.low;
    chBuf.utx.rf.raw.low = k;
    log_message("UTX:tag=%04X id=[%02X:%02X:%02X:%02X] ack=%d alrm=%d ad:=%d e:=%d\n", chBuf.utx.tag, chBuf.utx.rf.id[0], chBuf.utx.rf.id[1], chBuf.utx.rf.id[2], chBuf.utx.rf.id[3], chBuf.utx.rf.s.ack, chBuf.utx.rf.s.alrm, chBuf.utx.rf.s.ad, chBuf.utx.rf.s.e);
    if (chBuf.utx.tag!= SSTAG) {
        log_message("UTX:tag=%04X!=%04X error!!\n", chBuf.utx.tag, SSTAG);
        return 0;
    }
    FlutterDesktopMessengerSend(messager, "serialData", (const uint8_t *)chBuf.bytes, 7);
    return 0;
}
void log_bin(const void * data, int len) {
#if 0
    FILE * f = NULL;
    fopen_s(&f, "log.txt", "a+");
    if (f == NULL)   return ;
    for (BYTE * p = (BYTE*)data; p < (BYTE*)data + len; p++) {
        fprintf(f, "%02X ", *p);
    }
    fprintf(f, "\n");
    fclose(f);
#endif
}

void log_message(const char *format, ...) {
#if 0
    time_t current_time;
    struct tm local_time;
    va_list args;

    FILE * f = NULL;
    fopen_s(&f, "log.txt", "a+");

    if (f == NULL)   return ;
    time(&current_time);
    localtime_s(&local_time, &current_time);

    fprintf(f, "[%04d-%02d-%02d %02d:%02d:%02d.%03d] ",
           local_time.tm_year + 1900, local_time.tm_mon + 1, local_time.tm_mday,
           local_time.tm_hour, local_time.tm_min, local_time.tm_sec, GetTickCount() % 1000);

    va_start(args, format);
    vfprintf(f, format, args);
    va_end(args);

    fclose(f);
#endif
}